﻿using System.Collections;
using System.Linq;
using Habanero.Base;
using Habanero.BO;

namespace FireStarter.Base.BusinessObjectMerger
{
    public class SolutionTraverser
    {
        private IRelationship reverseRel;
/*        /// <summary>
        /// This is temp code at moment we do not have a wizard for importing ClassDefs into a Solution.
        /// So all Changes to the ClassDef are Assumed to be accepted.
        /// </summary>
        /// <param name="solution"></param>
        public void UpdateAllBOsAsPendingChangeAccepted(IMergableBo solution)
        {
            solution.PendingChangeApproved = true;
            foreach (IMergableBo dmAssembly in ((DMSolution)solution).Assemblies)
            {
                dmAssembly.PendingChangeApproved = true;
            }
        }*/

        public void MarkForDeleteAllChangeTypeDelete(IMergableBo parent)
        {
            MarkForDeleteAllChildrenChangeTypeDelete(parent);
            if (MustDelete(parent))
            {
                parent.MarkForDelete();
            }
        }

        private static bool MustDelete(IMergableBo parent)
        {
            return parent.PendingChangeType == ChangeType.Delete && parent.PendingChangeApproved;
        }

        private void MarkForDeleteAllChildrenChangeTypeDelete(IMergableBo parent)
        {
            var relationships = parent.Relationships
                .Where(relationship => !IsAssociation(relationship));
            foreach (var relationship in relationships)
            {
                if (IsOneToMany(relationship))
                {
                    MarkForDelete(((IMultipleRelationship) relationship).BusinessObjectCollection);
                }
                else if (IsOneToOne(relationship))
                {
                    var singleRelationship = (ISingleRelationship) relationship;
                    IMergableBo child = singleRelationship.GetRelatedObject() as IMergableBo;
                    if (child != null && MustDelete(child)) child.MarkForDelete();
                }
            }
        }

        private static bool IsOneToMany(IRelationship relationship)
        {
            return relationship.RelationshipDef.IsOneToMany;
        }

        private static bool IsOneToOne(IRelationship relationship)
        {
            return relationship is ISingleRelationship;
        }

        private static bool IsAssociation(IRelationship relationship)
        {
            return relationship.RelationshipDef.RelationshipType == RelationshipType.Association;
        }

        private void MarkForDelete(IEnumerable boChildren)
        {
            var children = boChildren.Cast<IMergableBo>().ToList();
            foreach (IMergableBo child in children)
            {
                if (MustDelete(child)) child.MarkForDelete();
                MarkForDeleteAllChildrenChangeTypeDelete(child);
            }
        }

        /// <summary>
        /// Traverses the Tree of Child Objects and marks any items that have a change type anything other than None as None
        /// </summary>
        /// <param name="parent"></param>
        public void MarkChangeTypeNone(IMergableBo parent)
        {
            MarkChangeType(parent, ChangeType.None);
        }

        /// <summary>
        /// Traverses the Tree of Child Objects and marks any items that have a change type anything other than Add to Add
        /// </summary>
        /// <param name="parent"></param>
        public void MarkChangeTypeAdd(IMergableBo parent)
        {
            MarkChangeType(parent, ChangeType.Add);
        }

        private void MarkChangeType(IMergableBo parent, ChangeType changeType)
        {
            if (parent == null) return;
            parent.PendingChangeType = changeType;
            var relationships = parent.Relationships
                .Where(relationship => !IsAssociation(relationship));
            foreach (var relationship in relationships)
            {
                if (IsOneToMany(relationship))
                {
                    var multipleRelationship = ((IMultipleRelationship) relationship);
                    MarkChangeType(multipleRelationship.BusinessObjectCollection, changeType);
                }
                else if (IsOneToOne(relationship))
                {
                    var singleRelationship = (ISingleRelationship) relationship;
                    MarkChangeType(singleRelationship.GetRelatedObject() as IMergableBo, changeType);
                }
            }
        }

        private void MarkChangeType(IEnumerable boChildren, ChangeType changeType)
        {
            var children = boChildren.Cast<IMergableBo>().ToList();
            foreach (IMergableBo child in children)
            {
                MarkChangeType(child, changeType);
            }
        }

        protected void SetParentPendingChangeApproved(IMergableBo currentBo, bool changeApproved)
        {

            if (currentBo == null) return;
            if (currentBo.PendingChangeType == ChangeType.None) return;
            currentBo.PendingChangeApproved = changeApproved;

            IMergableBo parentBo = GetParent(currentBo);
            if (parentBo == null) return;
            
            if (changeApproved) parentBo.PendingChangeApproved = true;

            if (changeApproved)
            {
                SetParentPendingChangeApproved(parentBo, true);
            }
            else if (!HasPendingChangesApproved(parentBo))
            {
                parentBo.PendingChangeApproved = false;
                SetParentPendingChangeApproved(parentBo, false);
            }

        }

        public void SetPendingChangeApproved(IMergableBo currentBo, bool changeApproved)
        {
            if (currentBo == null) return;
            SetChildrenPendingChangeApproved(currentBo, changeApproved);
            SetParentPendingChangeApproved(currentBo, changeApproved);
        }

        private void SetChildrenPendingChangeApproved(IMergableBo currentBo, bool changeApproved)
        {
            if (currentBo == null) return;
            if (currentBo.PendingChangeType == ChangeType.None) return;

            currentBo.PendingChangeApproved = changeApproved;            

            var relationships = currentBo.Relationships
                .Where(relationship => !IsAssociation(relationship));
            foreach (var relationship in relationships)
            {
                if (IsOneToMany(relationship))
                {
                    var multipleRelationship = ((IMultipleRelationship) relationship);
                    SetChildrenPendingChangeApproved(multipleRelationship.BusinessObjectCollection, changeApproved);
                }
                else if (IsOneToOne(relationship))
                {
                    var singleRelationship = (ISingleRelationship) relationship;
                    IMergableBo child = singleRelationship.GetRelatedObject() as IMergableBo;
                    SetChildrenPendingChangeApproved(child, changeApproved);
                }
            }
        }

        private void SetChildrenPendingChangeApproved(IEnumerable boChildren, bool changeApproved)
        {
            var children = boChildren.Cast<IMergableBo>().ToList();
            foreach (IMergableBo child in children)
            {
                SetPendingChangeApproved(child, changeApproved);
            }
        }


        protected bool HasPendingChangesApproved(IMergableBo currentBo)
        {
            bool hasChanges = false;
            if (currentBo == null) return false;

            var relationships = currentBo.Relationships
                    .Where(relationship => !IsAssociation(relationship));

            // no child relationships so evaluate immediately
            if (relationships.Count()==0)
            {
                hasChanges = (currentBo.PendingChangeApproved && currentBo.PendingChangeType != ChangeType.None);
            }
            else
            {
                foreach (var relationship in relationships)
                {
                    if (IsOneToMany(relationship))
                    {
                        var multipleRelationship = ((IMultipleRelationship)relationship);
                        hasChanges = HasPendingChangesApproved(multipleRelationship.BusinessObjectCollection);
                    }
                    else if (IsOneToOne(relationship))
                    {
                        var singleRelationship = (ISingleRelationship)relationship;
                        IMergableBo child = singleRelationship.GetRelatedObject() as IMergableBo;
                        hasChanges = HasPendingChangesApproved(child);
                    }
                    if (hasChanges) return true;
                }
            }
            return hasChanges;
        }

        private bool HasPendingChangesApproved(IEnumerable boChildren)
        {
            var children = boChildren.Cast<IMergableBo>();
            return children.Any(HasPendingChangesApproved);
        }

        protected IMergableBo GetParent(IMergableBo currentBO)
        {
            if (currentBO == null) return null;

            var relationships = currentBO.Relationships.Where(IsAssociation);

            foreach (var relationship in relationships)
            {
                var singleRelationship = relationship as ISingleRelationship;
                if (singleRelationship == null) continue;
                var potentialParent = singleRelationship.GetRelatedObject();
                if (IsRelatedObjectAParent(singleRelationship, potentialParent))
                {
                    return potentialParent as IMergableBo;
                }
            }
            return null;
        }

        private bool IsRelatedObjectAParent(ISingleRelationship singleRelationship, IBusinessObject potentialParent)
        {
            //If the reverse relationship is association then it cannot be 
            // the parent since the parent has an owner relationship to this 
            // bo which implies that it would have to have a composition or
            // an aggregation relationship to the child.
            reverseRel = singleRelationship.GetReverseRelationship(potentialParent);
            if (reverseRel == null) return false;
            if (IsAssociation(reverseRel)) return false;
            return true;
        }
    }
}